package helper

import (
	"bytes"
	"crypto"
	"crypto/aes"
	"crypto/cipher"
	"crypto/hmac"
	"crypto/md5"
	"crypto/rand"
	"crypto/rsa"
	"crypto/sha1"
	"crypto/sha256"
	"crypto/x509"
	"encoding/base64"
	"encoding/pem"
	"errors"
	"fmt"
	"io/ioutil"
	"reflect"
	"regexp"
	"sort"
	"strings"

	"golang.org/x/crypto/bcrypt"
)

// Sha1Encryption sha1加密
func Sha1Encryption(str string) string {
	h := sha1.New()
	h.Write([]byte(str))
	return fmt.Sprintf("%x", h.Sum(nil))
}

// GeneratePasswordHash 生成密码hash
func GeneratePasswordHash(pwd string) (string, error) {
	bytes, err := bcrypt.GenerateFromPassword([]byte(pwd), bcrypt.DefaultCost)
	return string(bytes), err
}

// VerifyPasswordHash 验证hash密码
func VerifyPasswordHash(hashStr, password string) bool {
	return bcrypt.CompareHashAndPassword([]byte(hashStr), []byte(password)) == nil
}

// Md5String md5字符串
func Md5String(str string) string {
	return fmt.Sprintf("%x", md5.Sum([]byte(str)))
}

// GenerateSignByHmacSha256 通过hmac-sha256加密生成签名
func GenerateSignByHmacSha256(data []byte, key []byte) string {
	hash := hmac.New(sha256.New, key)
	hash.Write(data)
	return fmt.Sprintf("%X", hash.Sum(nil))
}

// RsaSignWithSha256 签名: Sha256加密方式
func RsaSignWithSha256(dataBytes, keyBytes []byte) (sign string, err error) {
	if block, _ := pem.Decode(keyBytes); block == nil {
		err = errors.New("private key error")
	} else {
		// 使用 PKCS#8 私钥加密
		if priKey, e := x509.ParsePKCS8PrivateKey(block.Bytes); e != nil {
			// 使用 PKCS#1 私钥加密
			if priKey, e := x509.ParsePKCS1PrivateKey(block.Bytes); e != nil {
				err = e
			} else {
				h := sha256.New()
				h.Write(dataBytes)
				if signature, e := rsa.SignPKCS1v15(rand.Reader, priKey, crypto.SHA256, h.Sum(nil)); e != nil {
					err = e
				} else {
					sign = base64.StdEncoding.EncodeToString(signature)
				}
			}
		} else {
			h := sha256.New()
			h.Write(dataBytes)
			if signature, e := rsa.SignPKCS1v15(rand.Reader, priKey.(*rsa.PrivateKey), crypto.SHA256, h.Sum(nil)); e != nil {
				err = e
			} else {
				sign = base64.StdEncoding.EncodeToString(signature)
			}
		}
	}
	return
}

// VerifySignWithSha256 验签
func VerifySignWithSha256(sign string, dataBytes, keyBytes []byte) (ret bool, err error) {
	if signBytes, e := base64.StdEncoding.DecodeString(sign); e != nil {
		err = e
	} else if block, _ := pem.Decode(keyBytes); block == nil {
		err = errors.New("public key error")
	} else {
		if pubKey, e := x509.ParsePKIXPublicKey(block.Bytes); e != nil {
			err = e
		} else {
			h := sha256.New()
			h.Write(dataBytes)
			if err = rsa.VerifyPKCS1v15(pubKey.(*rsa.PublicKey), crypto.SHA256, h.Sum(nil), signBytes); err == nil {
				ret = true
			}
		}
	}
	return
}

// FormatKey 格式化公私钥
// @param  String  key  公私钥(文件名或公私钥字符串)
// @param  Uint8   t 	类型: 0 公钥, 1 私钥
// @return []byte, error
func FormatKey(key string, t uint8) (ret []byte, err error) {
	if PathExists(key) {
		return ioutil.ReadFile(key)
	}

	key = regexp.MustCompile(`\-+[\w\s]+\-+`).ReplaceAllString(key, "")
	key = regexp.MustCompile(`[\s]`).ReplaceAllString(key, "")
	lineNumber := 64
	_ret := ""
	if t == 1 {
		_ret = "-----BEGIN RSA PRIVATE KEY-----" + "\n"
	} else {
		_ret = "-----BEGIN PUBLIC KEY-----" + "\n"
	}
	for {
		_ret += key[:lineNumber] + "\n"
		key = key[lineNumber:]
		if len(key) <= lineNumber {
			_ret += key + "\n"
			break
		}
	}
	if t == 1 {
		_ret += "-----END RSA PRIVATE KEY-----"
	} else {
		_ret += "-----END PUBLIC KEY-----"
	}
	return []byte(_ret), nil
}

// FormatToMap 格式化成map
// @param  *struct  x
// @param  string   tagName
// @return map[string]string
func FormatToMap(x interface{}, tagName string) map[string]string {
	ret := map[string]string{}
	for k, v := range FormatToMapInterface(x, tagName) {
		ret[k] = InterfaceToString(v)
	}
	return ret
}

func FormatToMapInterface(x interface{}, tagName string) map[string]interface{} {
	ret := map[string]interface{}{}
	rType := reflect.TypeOf(x)
	isPtr := false
	if rType.Kind() == reflect.Ptr {
		isPtr = true
		rType = rType.Elem()
	}
	if rType.Kind() == reflect.Struct {
		vType := reflect.ValueOf(x)
		if isPtr {
			vType = vType.Elem()
		}
		for i := 0; i < rType.NumField(); i++ {
			var tag, key string
			for _, v := range strings.Split(tagName, "|") {
				if tag = rType.Field(i).Tag.Get(v); tag != "" {
					break
				}
			}
			for _, v := range strings.Split(tag, ",") {
				v = strings.TrimSpace(v)
				if v != "" && v != "-" && v != "omitempty" {
					key = v
					break
				}
			}
			if key != "" && (!vType.Field(i).IsZero() || !strings.Contains(","+tag+",", ",omitempty,")) {
				ret[key] = vType.Field(i).Interface()
			}
		}
	}
	return ret
}

// GetSignContent 获取签名内容
func GetSignContent(data map[string]string, filterField string) (ret string) {
	arr := []string{}
	if filterField != "" {
		filterField = "," + filterField + ","
	} else {
		filterField = ",sign,"
	}
	for k, v := range data {
		if Trim(v) != "" && v[:1] != "@" && !strings.Contains(filterField, ","+k+",") {
			arr = append(arr, k)
		}
	}
	if len(arr) > 0 {
		sort.Strings(arr)
		for _, v := range arr {
			ret += "&" + v + "=" + data[v]
		}
		ret = ret[1:]
	}
	return
}

// 对称秘钥长度必须是为: 16, 24, 32
func formatAesKey(key string) []byte {
	length := len(key)
	defaultLen := 32

	if length == 16 || length == 24 || length == 32 {
		return []byte(key)
	} else if length > defaultLen {
		key = key[:defaultLen]
	} else if length < defaultLen {
		for length < defaultLen {
			key += "~" // 补足缺少的字符(任意字符, 加解密必须一致)
			length++
		}
	}
	return []byte(key)
}

// AES加密
func AesEncrypt(origStr, keyStr string) (string, error) {
	key := formatAesKey(keyStr)
	block, err := aes.NewCipher(key)
	if err != nil {
		return "", err
	}
	blockSize := block.BlockSize()
	origData := []byte(origStr)
	padding := blockSize - len(origData)%blockSize
	padtext := bytes.Repeat([]byte{byte(padding)}, padding)
	origData = append(origData, padtext...)
	blockMode := cipher.NewCBCEncrypter(block, key[:blockSize])
	crypted := make([]byte, len(origData))
	blockMode.CryptBlocks(crypted, origData)
	return base64.StdEncoding.EncodeToString(crypted), nil
}

// AES解密
func AesDecrypt(crypted, keyStr string) (string, error) {
	var (
		err                   error
		crypteBytes, origData []byte
		block                 cipher.Block
	)

	if crypteBytes, err = base64.StdEncoding.DecodeString(crypted); err == nil {
		key := formatAesKey(keyStr)
		if block, err = aes.NewCipher(key); err == nil {
			blockSize := block.BlockSize()
			blockMode := cipher.NewCBCDecrypter(block, key[:blockSize])
			origData = make([]byte, len(crypteBytes))
			blockMode.CryptBlocks(origData, crypteBytes)
			length := len(origData)
			origData = origData[:(length - int(origData[length-1]))]
		}
	}
	return string(origData), err
}
